/*
 * Copyright (c) 2012 Jonathan Tyers
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package uk.org.uplink.configproxy;

import java.io.File;
import java.lang.reflect.Proxy;
import java.net.URL;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.configuration.XMLConfiguration;

/**
 * Factory class for creating config-proxies.
 * <p>
 * The most common way to use this class is to call the appropriate static method directly:
 * 
 * <pre>
 * // given that MyConfiguration is an interface annotated with {@code @}ConfigProxy
 * MyConfiguration configuration = ConfigProxyFactory.createConfigProxyFromXml(MyConfiguration.class, &quot;/path/to/file.xml&quot;);
 * </pre>
 * 
 * This class can be called directly to create proxies, or can be tied into a dependency-injection framework to provide automatic injection of
 * proxies.
 * 
 * @see ConfigProxy
 * @author jonny
 */
public class ConfigProxyFactory {
	/**
	 * Creates a proxy implementing the given interface class, whose methods retrieve values from the given XML file.
	 * <p>
	 * <tt>interfaze</tt> must represent a class that has the {@code @}{@link ConfigProxy} annotation. Only methods annotated with {@code @}
	 * {@link ConfigValue} will return values looked up from the configuration.
	 * 
	 * @param interfaze
	 *            the {@code @}ConfigProxy interface to create an implementation for
	 * @param xmlFile
	 *            the XML file the config proxy should load values from
	 * @return
	 */
	public static <T> T createConfigProxyFromXml(final Class<T> interfaze, final String xmlFile) throws ConfigurationException {
		return createConfigProxyFromXml(interfaze, new File(xmlFile));
	}

	/**
	 * Creates a proxy implementing the given interface class, whose methods retrieve values from the given XML file.
	 * <p>
	 * <tt>interfaze</tt> must represent a class that has the {@code @}{@link ConfigProxy} annotation. Only methods annotated with {@code @}
	 * {@link ConfigValue} will return values looked up from the configuration.
	 * 
	 * @param interfaze
	 *            the {@code @}ConfigProxy interface to create an implementation for
	 * @param xmlFile
	 *            the XML file the config proxy should load values from
	 * @return
	 */
	public static <T> T createConfigProxyFromXml(final Class<T> interfaze, final File xmlFile) throws ConfigurationException {
		XMLConfiguration configuration = new XMLConfiguration(xmlFile);
		return new ConfigProxyFactory().createConfigProxy(interfaze, configuration);
	}

	/**
	 * Creates a proxy implementing the given interface class, whose methods retrieve values from the given XML file.
	 * <p>
	 * <tt>interfaze</tt> can optionally use {@code @}{@link ConfigProxy} to customise behaviour, and
	 * its methods may use {@code @}{@link ConfigValue} to customise their behaviour.
	 * 
	 * @param interfaze
	 *            the {@code @}ConfigProxy interface to create an implementation for
	 * @param xmlFile
	 *            the XML file the config proxy should load values from
	 * @return
	 */
	public static <T> T createConfigProxyFromXml(final Class<T> interfaze, final URL xmlFile) throws ConfigurationException {
		XMLConfiguration configuration = new XMLConfiguration(xmlFile);
		return new ConfigProxyFactory().createConfigProxy(interfaze, configuration);
	}

	/**
	 * Create a new implementation of the given interface. The supplied {@link HierarchicalConfiguration} is used to provide values for methods on the interface.
	 * <p>
	 * <tt>interfaze</tt> can optionally use {@code @}{@link ConfigProxy} to customise behaviour, and
	 * its methods may use {@code @}{@link ConfigValue} to customise their behaviour.
	 * 
	 * @param interfaze
	 * @param resolver
	 * @return
	 */
	public static <T> T createConfigProxyFromConfiguration(final Class<T> interfaze, final HierarchicalConfiguration configuration) {
		return new ConfigProxyFactory().createConfigProxy(interfaze, configuration);
	}

	/**
	 * Create a new implementation of the given interface. The supplied {@link HierarchicalConfiguration} is used to provide values for methods on the interface.
	 * <p>
	 * <tt>interfaze</tt> can optionally use {@code @}{@link ConfigProxy} to customise behaviour, and
	 * its methods may use {@code @}{@link ConfigValue} to customise their behaviour.
	 * 
	 * @param interfaze
	 * @param resolver
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T createConfigProxy(final Class<T> interfaze, final HierarchicalConfiguration configuration) {
		ConfigValueResolver resolver = ConfigValueResolver.create(interfaze, configuration);

		return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { interfaze }, resolver);
	}
}
